package eu.catlab.bbgs;

import java.io.File;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import eu.catlab.bbgs.attachments.Attachment;
import eu.catlab.bbgs.attachments.Image;
import eu.catlab.bbgs.interfaces.Parameter;
import eu.catlab.bbgs.interfaces.User;

public class Notification 
{
	private String message;
	private Date timestamp;
	
	private String id;
	private String group;
	private String description;
	
	private String skeleton;
	
	private String xml;
	private String visibility;
	private String language;
	
	private File privatekey;
	private String callback;
	
	private List<Attachment> attachments = new ArrayList<Attachment> ();
	private List<Parameter> parameters = new ArrayList<Parameter> ();
	
	private User sender;
	
	
	
	public Notification (String message, String language)
	{
		setMessage (message);
		setTimestamp (new Date());
		setLanguage (language);
	}
	
	/**
	 * 
	 * @param message to send
	 * @param timestamp
	 */
	public Notification (String message)
	{
		this (message, "en");
	}
	
	
	public void addAttachment (Attachment attachment)
	{
		attachments.add(attachment);
	}
	
	
	public void addParameter (Parameter parameter)
	{
		parameters.add(parameter);
	}
	
	public void addParameter (String parameter)
	{
		addParameter (new ParameterString (parameter));
	}
	
	/**
	 * This method can be used to specify who is sending this message.
	 * 
	 * Use this method to set the sender of this message. You can use tis
	 * to specify a player to player action (for example, "you have received 
	 * a message from x"). Some social networks provide a limit of server-to-user
	 * messages, the user-to-user message tends to be a lot higher.
	 * 
	 * @param sender The sending user (or "actor").
	 */
	public void setSender (User sender)
	{
		this.sender = sender;
	}
	
	/**
	 * Send the message to the openid provider.
	 * 
	 * This method will first generate the required XML
	 * and sign the content so that you are the only one
	 * who can send the notifications.
	 * 
	 * @param target The target user object
	 * @param notify_url The URL as received in the AX attribute from your OpenID provider
	 * @param signature The signature to sign the request
	 * @throws ParserConfigurationException 
	 */
	public void send (User target, String notify_url, Signature signature) throws ParserConfigurationException
	{
		// First let's make the xml 
		DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
		Document doc = docBuilder.newDocument();
		
		Element root = doc.createElement ("notification");
		root.setAttribute("version", "1");
		root.setAttribute("visibility", getVisibility());
		root.setAttribute("lang", getLanguage());
		
		addSimpleElement (doc, root, "text", getMessage());
		addSimpleElement (doc, root, "id", getId());
		addSimpleElement (doc, root, "group", getGroup());
		addSimpleElement (doc, root, "skeleton", getSkeleton());
		addSimpleElement (doc, root, "description", getDescription());
		
		Element pars = doc.createElement("arguments");
		root.appendChild(pars);
		
		for (Parameter p : parameters)
		{
			pars.appendChild(translateAttribute (doc, p));
		}
		
		if (sender != null)
		{
			Element senderobj = doc.createElement("sender");
			fillUserElement(doc, senderobj, sender);
			root.appendChild(senderobj);
		}
		
		Element targetobj = doc.createElement("target");
		fillUserElement(doc, targetobj, target);
		root.appendChild(targetobj);
		
		Element attachmentobj = doc.createElement ("attachments");
		root.appendChild (attachmentobj);
		
		for (Attachment a : attachments)
		{
			Element att = doc.createElement ("attachment");
			
			if (a instanceof Image)
			{
				att.setAttribute("type", "image");
				addSimpleElement (doc, att, "src", ((Image) a).getSource());
				addSimpleElement (doc, att, "name", ((Image) a).getName());
				addSimpleElement (doc, att, "link", ((Image) a).getLink());
			}
			
			attachmentobj.appendChild (att);
		}
		
		doc.appendChild(root);
		String xml = xmlToString (doc);
		
		String postdata = "";
		
		try
		{
			postdata += "text=" + URLEncoder.encode(getMessage(), "UTF-8") + "&";
			postdata += "date=" + URLEncoder.encode(getTimestamp().toGMTString(), "UTF-8") + "&";
			postdata += "xml=" + URLEncoder.encode(xml, "UTF-8") + "&";
			postdata += "signature=" + URLEncoder.encode(signature.getSignature(xml), "UTF-8");
		}
		catch (Exception e)
		{
			System.out.println (e);
		}
		
		String sign = signature.getSignature(postdata);
		System.out.println (Util.file_post_content(notify_url, postdata));
	}
	
    public static String xmlToString(Node node) {
        try {
            Source source = new DOMSource(node);
            StringWriter stringWriter = new StringWriter();
            Result result = new StreamResult(stringWriter);
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.transform(source, result);
            return stringWriter.getBuffer().toString();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        return null;
    }
	
	private static String getHexString(byte[] b)
	{
		  String result = "";
		  for (int i=0; i < b.length; i++) {
		    result +=
		          Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
		  }
		  return result;
		}
	
	private String md5 (String s)
	{
		byte[] hash = null;
		
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");

			md.update(s.getBytes());
			hash = md.digest();
		
		} 
		catch (NoSuchAlgorithmException e) 
		{
			e.printStackTrace();
		}
		
		return getHexString (hash);
	}
	
	private Element translateAttribute (Document doc, Parameter p)
	{
		Element arg = doc.createElement ("argument");
		
		if (p instanceof User)
		{
			addSimpleElement (doc, arg, "value", ((User) p).getName());
			fillUserElement (doc, arg, (User)p);
			arg.setAttribute ("type", "user");
		}
		
		else if (p instanceof ParameterString)
		{
			addSimpleElement (doc, arg, "value", ((ParameterString) p).toString());
			arg.setAttribute("type", "text");
		}
		
		return arg;
	}
	
	private void fillUserElement (Document doc, Element arg, User p)
	{
		addSimpleElement (doc, arg, "member_id", p.getId());
		addSimpleElement (doc, arg, "member_url", p.getProfileURL());
		addSimpleElement (doc, arg, "name", p.getName());
		addSimpleElement (doc, arg, "score", "" + p.getScore());
		
		Element openids = doc.createElement("openids");
		arg.appendChild(openids);
		
		for (String openid : p.getOpenIDs())
		{
			Element oid = doc.createElement("openid");
			oid.setAttribute("hash", "md5");
			oid.setNodeValue(md5 (openid));
			openids.appendChild(oid);
		}
		
		addSimpleElement (doc, arg, "join_date", p.getJoinDate().toGMTString());
	}
	
	private void addSimpleElement (Document doc, Element root, String name, String value)
	{
		Element element = doc.createElement (name);
		element.setTextContent(value);
		root.appendChild (element);
	}
	
	

	
	/**
	 * Getters and setters
	 */
	

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(Date timestamp) {
		this.timestamp = timestamp;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getGroup() {
		return group;
	}

	public void setGroup(String group) {
		this.group = group;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getSkeleton() {
		return skeleton;
	}

	public void setSkeleton(String skeleton) {
		this.skeleton = skeleton;
	}

	public String getVisibility() {
		return visibility;
	}

	public void setVisibility(String visibility) {
		this.visibility = visibility;
	}

	public String getLanguage() {
		return language;
	}

	public void setLanguage(String language) {
		this.language = language;
	}

	public File getPrivatekey() {
		return privatekey;
	}

	public void setPrivatekey(File privatekey) {
		this.privatekey = privatekey;
	}
}
